/**
 * 
 */
package com.dalonedrau.jogl.opengl.ui;

import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Polygon;
import java.awt.event.MouseEvent;

import javax.swing.SwingUtilities;

import com.dalonedrau.jogl.engine.Animation;
import com.dalonedrau.jogl.engine.GameDrawable;
import com.dalonedrau.jogl.engine.GlobalConsts;
import com.dalonedrau.jogl.engine.Interactive;
import com.dalonedrau.jogl.engine.Interface;
import com.dalonedrau.jogl.engine.JOGLErrorHandler;
import com.dalonedrau.jogl.engine.Render;
import com.dalonedrau.jogl.engine.Sprite;
import com.dalonedrau.jogl.engine.Time;
import com.dalonedrau.jogl.opengl.animation.AnimationSequenceObjectFactory;
import com.dalonedrau.jogl.opengl.sprite.BasicSpriteObject;
import com.dalonedrau.jogl.opengl.sprite.SpriteBase;
import com.dalonedrau.jogl.opengl.sprite.SpriteImageObject;
import com.dalonedrau.jogl.opengl.sprite.SpriteImageObjectFactory;
import com.dalonedrau.jogl.opengl.sprite.SpriteTileMap;
import com.dalonedrau.map.TileMap;
import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.dalonedrau.vectors.Vector2;
import com.dalonedrau.vectors.Vector3;
import com.module.bp.bus.BPHexMap;
import com.module.bp.engine.Turn;
import com.module.bp.event.base.EndPhaseEvent;
import com.module.bp.event.base.LandTravelEvent;
import com.module.bp.player.BPParty;
import com.module.bp.ui.ActionMenu;
import com.module.bp.view.OverworldView;
import com.rpg.base.interactive.flyweight.BaseInteractiveObject;

/**
 * The Overworld map has XX modes: Mode 1 - allows for panning and clicking on
 * the party Mode 2 - displays the action menu Mode 3 - processes the action
 * @author DaLoneDrau
 */
public final class OverworldMap extends PanningMap {
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void centerMapOnIO(int refid) {
		// TODO Auto-generated method stub
		
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected int isMouseOverIO(float x, float y) {
		// TODO Auto-generated method stub
		return 0;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected boolean isMouseOverIO(int refid, float x, float y) {
		// TODO Auto-generated method stub
		return false;
	}
	private SpriteBase				hexHighlight;
	private SpriteBase[]			hexLowlights	= new SpriteBase[550];
	/** defines the borders for all possible travel locations. */
	private TravelOption[]			hexShapes		= new TravelOption[36];
	/** flag indicating the party is in raft travel mode. */
	private boolean					isFloating		= false;
	/** the map layers that comprise the overworld map. */
	private SpriteTileMap[]			layers;
	/** the size of the overworld map in the number of tiles . */
	private Dimension				mapSize;
	/** the party hover icon. */
	private BaseInteractiveObject	partyHoverIcon;
	/** the size of the UI component, in pixels. */
	private Dimension				size;
	/** the left coordinate where the view starts. */
	// private int viewX = 0;
	/** the bottom coordinate where the view starts. */
	// private int viewY = 0;
	/** Creates a new instance of {@link OverworldMap}. */
	public OverworldMap() {
		this(0, 0);
	}
	/**
	 * Creates a new instance of {@link OverworldMap}.
	 * @param width the width of the component
	 * @param height the height of the component
	 */
	public OverworldMap(final int width, final int height) {
		size = new Dimension(width, height);
		initializePartyHoverCursor();
	}
	/**
	 * Applies additional shaded hexes for the "missing" hexes on the map; the
	 * map hexes on the edges that the party cannot travel to. Returns the index
	 * of the next available "extra" hex for shading.
	 * @param index the index of the hex being checked to add shading for the
	 *        "missing" edges
	 * @param spriteLeftX the hex's sprite's left x-coordinate
	 * @param spriteBottomY the hex's sprite's bottom y-coordinate
	 * @param z the hex's sprite's z-index
	 * @param lastExtra the index of the last "extra" hex that was shaded
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	private int applyAdditionalHexShading(final int index,
			final double spriteLeftX, final double spriteBottomY,
			final double z, final int lastExtra) throws Exception {
		int lastExtraIndex = lastExtra;
		if (index < 20) { // need top row shading
			// do we need a the top-center hex shadowed?
			if (index % 2 == 1) {
				double newBottomY = spriteBottomY + 64;
				double newLeftX = spriteLeftX;
				// is this sprite visible?
				if (Sprite.getInstance().contains(
						(int) newLeftX, (int) newBottomY, 64, 64,
						(int) super.getPosition().getX(),
						(int) super.getPosition().getY(),
						size.width, size.height)
						|| Sprite.getInstance().intersects(
								(int) newLeftX, (int) newBottomY, 64, 64,
								(int) super.getPosition().getX(),
								(int) super.getPosition().getY(),
								size.width, size.height)) {
					SpriteBase sb = hexLowlights[lastExtraIndex++];
					sb.setPosition(newLeftX, newBottomY, z);
					Sprite.getInstance().setPartiallyRendered(
							sb,
							newBottomY + hexLowlights[index].getHeight(),
							newLeftX,
							newBottomY,
							newLeftX + hexLowlights[index].getWidth(),
							super.getPosition().getY() + size.height,
							super.getPosition().getX(),
							super.getPosition().getY(),
							super.getPosition().getX() + size.width);
				}
			}
		}
		if (index % 20 == 0) { // need left column highlights
			double newLeftX = spriteLeftX - 48;
			double newBottomY = spriteBottomY + 32;
			// is this sprite visible?
			if (Sprite.getInstance().contains(
					(int) newLeftX, (int) newBottomY, 64, 64,
					(int) super.getPosition().getX(),
					(int) super.getPosition().getY(),
					size.width, size.height)
					|| Sprite.getInstance().intersects(
							(int) newLeftX, (int) newBottomY, 64, 64,
							(int) super.getPosition().getX(),
							(int) super.getPosition().getY(),
							size.width, size.height)) {
				SpriteBase sb = hexLowlights[lastExtraIndex++];
				sb.setPosition(newLeftX, newBottomY, z);
				Sprite.getInstance().setPartiallyRendered(
						sb,
						newBottomY + hexLowlights[index].getHeight(),
						newLeftX,
						newBottomY,
						newLeftX + hexLowlights[index].getWidth(),
						super.getPosition().getY() + size.height,
						super.getPosition().getX(),
						super.getPosition().getY(),
						super.getPosition().getX() + size.width);
			}
			if (index == 440) { // add a second hex shade for bottom left side
				newBottomY = spriteBottomY - 32;
				// is this sprite visible?
				if (Sprite.getInstance().contains(
						(int) newLeftX, (int) newBottomY, 64, 64,
						(int) super.getPosition().getX(),
						(int) super.getPosition().getY(),
						size.width, size.height)
						|| Sprite.getInstance().intersects(
								(int) newLeftX, (int) newBottomY, 64, 64,
								(int) super.getPosition().getX(),
								(int) super.getPosition().getY(),
								size.width, size.height)) {
					SpriteBase sb = hexLowlights[lastExtraIndex++];
					sb.setPosition(newLeftX, newBottomY, z);
					Sprite.getInstance().setPartiallyRendered(
							sb,
							newBottomY + hexLowlights[index].getHeight(),
							newLeftX,
							newBottomY,
							newLeftX + hexLowlights[index].getWidth(),
							super.getPosition().getY() + size.height,
							super.getPosition().getX(),
							super.getPosition().getY(),
							super.getPosition().getX() + size.width);
				}
			}
		}
		if (index >= 440) {
			// need bottom row highlights
			if (index % 2 == 0) {
				double newBottomY = spriteBottomY - 64;
				double newLeftX = spriteLeftX;
				// is this sprite visible?
				if (Sprite.getInstance().contains(
						(int) newLeftX, (int) newBottomY, 64, 64,
						(int) super.getPosition().getX(),
						(int) super.getPosition().getY(),
						size.width, size.height)
						|| Sprite.getInstance().intersects(
								(int) newLeftX, (int) newBottomY, 64, 64,
								(int) super.getPosition().getX(),
								(int) super.getPosition().getY(),
								size.width, size.height)) {
					SpriteBase sb = hexLowlights[lastExtraIndex++];
					sb.setPosition(newLeftX, newBottomY, z);
					Sprite.getInstance().setPartiallyRendered(
							sb,
							newBottomY + hexLowlights[index].getHeight(),
							newLeftX,
							newBottomY,
							newLeftX + hexLowlights[index].getWidth(),
							super.getPosition().getY() + size.height,
							super.getPosition().getX(),
							super.getPosition().getY(),
							super.getPosition().getX() + size.width);
				}
			}
		}
		if (index % 20 == 19) {
			// need right column highlights
			double newLeftX = spriteLeftX + 48;
			double newBottomY = spriteBottomY + 32;
			// is this sprite visible?
			if (Sprite.getInstance().contains(
					(int) newLeftX, (int) newBottomY, 64, 64,
					(int) super.getPosition().getX(),
					(int) super.getPosition().getY(),
					size.width, size.height)
					|| Sprite.getInstance().intersects(
							(int) newLeftX, (int) newBottomY, 64, 64,
							(int) super.getPosition().getX(),
							(int) super.getPosition().getY(),
							size.width, size.height)) {
				SpriteBase sb = hexLowlights[lastExtraIndex++];
				sb.setPosition(newLeftX, newBottomY, z);
				Sprite.getInstance().setPartiallyRendered(
						sb,
						newBottomY + hexLowlights[index].getHeight(),
						newLeftX,
						newBottomY,
						newLeftX + hexLowlights[index].getWidth(),
						super.getPosition().getY() + size.height,
						super.getPosition().getX(),
						super.getPosition().getY(),
						super.getPosition().getX() + size.width);
			}
			if (index == 459) { // add a second hex shade for bottom left side
				newBottomY = spriteBottomY - 32;
				// is this sprite visible?
				if (Sprite.getInstance().contains(
						(int) newLeftX, (int) newBottomY, 64, 64,
						(int) super.getPosition().getX(),
						(int) super.getPosition().getY(),
						size.width, size.height)
						|| Sprite.getInstance().intersects(
								(int) newLeftX, (int) newBottomY, 64, 64,
								(int) super.getPosition().getX(),
								(int) super.getPosition().getY(),
								size.width, size.height)) {
					SpriteBase sb = hexLowlights[lastExtraIndex++];
					sb.setPosition(newLeftX, newBottomY, z);
					Sprite.getInstance().setPartiallyRendered(
							sb,
							newBottomY + hexLowlights[index].getHeight(),
							newLeftX,
							newBottomY,
							newLeftX + hexLowlights[index].getWidth(),
							super.getPosition().getY() + size.height,
							super.getPosition().getX(),
							super.getPosition().getY(),
							super.getPosition().getX() + size.width);
				}
			}
		}
		return lastExtraIndex;
	}
	/**
	 * Centers the map on a specific hex.
	 * @param x the hex's x-position
	 * @param y the hex's y-position
	 */
	public void centerMapOnHex(final int x, final int y) {
		Vector2 pt = BPHexMap.getInstance().getHexCenterPoint(x, y);
		super.setViewX((int) pt.getX() - size.width / 2);
		super.setViewY((int) pt.getY() - size.height / 2);
		if (super.getViewX() < 0) {
			super.setViewX(0);
		} else if (super.getViewX() > mapSize.width * 16 - size.width) {
			super.setViewX(mapSize.width * 16 - size.width);
		}
		if (super.getViewY() < 0) {
			super.setViewY(0);
		} else if (super.getViewY() > mapSize.height * 16 - size.height) {
			super.setViewY(mapSize.height * 16 - size.height);
		}
		for (int i = 0; i < layers.length; i++) {
			layers[i].setViewX(super.getViewX());
			layers[i].setViewY(super.getViewY());
			layers[i].setFloatStoreNumber(0);
		}
	}
	/**
	 * Centers the map on a specific hex.
	 * @param v the hex's location
	 */
	public void centerMapOnHex(final Vector2 v) {
		centerMapOnHex((int) v.getX(), (int) v.getY());
	}
	/**
	 * Checks to see if panning is possible, and assigns the direction and
	 * cursor rendered. Panning is not possible when the map has reached its
	 * borders.
	 * @param x the x-coordinate of the mouse pointer
	 * @param y the y-coordinate of the mouse pointer
	 * @return true if panning is possible; false otherwise
	 */
	/*
	 * private boolean checkForPanning(final float x, final float y) { boolean
	 * panPossible = false; double inrx = super.getPosition().getX() +
	 * OverworldMap.EDGE; double inry = super.getPosition().getY() +
	 * OverworldMap.EDGE; // determine which edge? if (x < inrx) { // NNW, SSW,
	 * W if (y < inry) { // SSW // is it possible to go pan SSW if (viewX > 0 &&
	 * viewY > 0) { panPossible = true; panningDirection = OverworldMap.SSW;
	 * GameDrawable.setCursor( GameDrawable.PAN_SOUTHWEST_CURSOR); } else if
	 * (viewX > 0) { panPossible = true; panningDirection = OverworldMap.W;
	 * GameDrawable.setCursor( GameDrawable.PAN_WEST_CURSOR); } else if (viewY >
	 * 0) { panPossible = true; panningDirection = OverworldMap.S;
	 * GameDrawable.setCursor( GameDrawable.PAN_SOUTH_CURSOR); } } else if (y >
	 * inry + size.height - OverworldMap.EDGE * 2) { // NNW if (viewX > 0 &&
	 * viewY < mapSize.height * 16 - size.height) { panPossible = true;
	 * panningDirection = OverworldMap.NNW; GameDrawable.setCursor(
	 * GameDrawable.PAN_NORTHWEST_CURSOR); } else if (viewX > 0) { panPossible =
	 * true; panningDirection = OverworldMap.W; GameDrawable.setCursor(
	 * GameDrawable.PAN_WEST_CURSOR); } else if (viewY < mapSize.height * 16 -
	 * size.height) { panPossible = true; panningDirection = OverworldMap.N;
	 * GameDrawable.setCursor( GameDrawable.PAN_NORTH_CURSOR); } } else { // W
	 * // is it possible to go pan W if (viewX > 0) { panPossible = true;
	 * panningDirection = OverworldMap.W; GameDrawable.setCursor(
	 * GameDrawable.PAN_WEST_CURSOR); } } } else if (x > inrx + size.width -
	 * OverworldMap.EDGE * 2) { // NNE, SSE, E if (y < inry) { // SSE if (viewX
	 * < mapSize.width * 16 - size.width && viewY > 0) { panPossible = true;
	 * panningDirection = OverworldMap.SSE; GameDrawable.setCursor(
	 * GameDrawable.PAN_SOUTHEAST_CURSOR); } else if (viewX < mapSize.width * 16
	 * - size.width) { panPossible = true; panningDirection = OverworldMap.E;
	 * GameDrawable.setCursor( GameDrawable.PAN_EAST_CURSOR); } else if (viewY >
	 * 0) { panPossible = true; panningDirection = OverworldMap.S;
	 * GameDrawable.setCursor( GameDrawable.PAN_SOUTH_CURSOR); } } else if (y >
	 * inry + size.height - OverworldMap.EDGE * 2) { // NNE if (viewX <
	 * mapSize.width * 16 - size.width && viewY < mapSize.height * 16 -
	 * size.height) { panPossible = true; panningDirection = OverworldMap.NNE;
	 * GameDrawable.setCursor( GameDrawable.PAN_NORTHEAST_CURSOR); } else if
	 * (viewX < mapSize.width * 16 - size.width) { panPossible = true;
	 * panningDirection = OverworldMap.E; GameDrawable.setCursor(
	 * GameDrawable.PAN_EAST_CURSOR); } else if (viewY < mapSize.height * 16 -
	 * size.height) { panPossible = true; panningDirection = OverworldMap.N;
	 * GameDrawable.setCursor( GameDrawable.PAN_NORTH_CURSOR); } } else { // E
	 * if (viewX < mapSize.width * 16 - size.width) { panPossible = true;
	 * panningDirection = OverworldMap.E; GameDrawable.setCursor(
	 * GameDrawable.PAN_EAST_CURSOR); } } } else if (y < inry) { // S if (viewY
	 * > 0) { panPossible = true; panningDirection = OverworldMap.S;
	 * GameDrawable.setCursor(GameDrawable.PAN_SOUTH_CURSOR); } } else { // N if
	 * (viewY < mapSize.height * 16 - size.height) { panPossible = true;
	 * panningDirection = OverworldMap.N;
	 * GameDrawable.setCursor(GameDrawable.PAN_NORTH_CURSOR); } } return
	 * panPossible; }
	 */
	/** Sets the flags to display the action menu. */
	public void displayActionMenu() {
		OverworldView.getInstance().removeFlag(
				OverworldView.OWV_ALLOW_ACTION_MENU);
		OverworldView.getInstance().assignFlag(
				OverworldView.OWV_SHOW_ACTION_MENU);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Dimension getPreferredSize() {
		return size;
	}
	/**
	 * Handles events that occur when the map is clicked on while in action menu
	 * mode.
	 * @param e the {@link MouseEvent}
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	protected void handleMCActionMenuMode(final MouseEvent e, final float x,
			final float y) {
		if (SwingUtilities.isRightMouseButton(e)) {
			// RIGHT BUTTON CLICKED
			// 1- HIDE ACTION MENU AGAIN
			hideActionMenu();
			// 2- SET MAP MODE BACK TO FREE MODE
			OverworldView.getInstance().setMapMode(
					GlobalConsts.MAP_MODE_00_FREE);
		} else {
			// LET ACTION MENU HANDLE CLICK
			ActionMenu.getInstance().handleMouseClick(e, x, y);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMCFreeMode(final MouseEvent e, final float x,
			final float y) throws Exception {
		if (OverworldView.getInstance().hasFlag(
				OverworldView.OWV_ALLOW_ACTION_MENU)
				&& isMouseOverPartySprite(x, y)
				&& !SwingUtilities.isRightMouseButton(e)) {
			// PARTY WAS LEFT-CLICKED
			// 1- CENTER MAP ON PARTY
			OverworldView.getInstance().centerMapOnParty();
			// 2- FIGURE OUT PARTY SPRITE'S ONSCREEN POSITION
			double ax = BPParty.getInstance().getWorldMapSprite()
					.getSprite().getPosition().getX();
			ax -= super.getViewX();
			ax += super.getPosition().getX();
			double ay = BPParty.getInstance().getWorldMapSprite()
					.getSprite().getPosition().getY();
			ay -= super.getViewY();
			ay += super.getPosition().getY();
			double az = 0;
			if (super.getPosition() != null) {
				az = super.getPosition().getZ();
			}
			az += GlobalConsts.CHILD_STEP * layers.length;
			partyHoverIcon.getSprite().setPosition(ax, ay, az);
			// 3- SET MODE TO ACTION MENU
			super.setMode(GlobalConsts.MAP_MODE_01_ACTION_MENU);
			// 4- POSITION ACTION MENU
			ActionMenu.getInstance().displayActions(ax, ay,
					super.getPosition().getX(), super.getPosition().getY(),
					size.width, size.height);
			// 5- DISPLAY ACTION MENU
			displayActionMenu();
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMCMoveSelectionMode(final MouseEvent e, final float x,
			final float y) {
		if (SwingUtilities.isRightMouseButton(e)) {
			// RIGHT-CLICK DURING MOVE SELECTION:
			if (BPParty.getInstance().getNumMovesTaken() < BPParty
					.getInstance().getNumMovesPossible()) {
				// PARTY HAS TAKEN A MOVE
				// END THE PHASE
				int movesLeft = BPParty.getInstance().getNumMovesPossible();
				movesLeft -= BPParty.getInstance().getNumMovesTaken();
				PooledStringBuilder sb =
						StringBuilderPool.getInstance().getStringBuilder();
				sb.append("You can still move ");
				sb.append(movesLeft);
				if (movesLeft > 1) {
					sb.append(" spaces.  ");
				} else {
					sb.append(" space.  ");
				}
				sb.append("Do you wish to stop for the day?");
				Interface.getInstance().showSmallOptionsMessageWindow(
						"Travel",
						sb.toString(),
						new EndPhaseEvent(),
						null
						);
				sb.returnToPool();
			} else {
				// PARTY HAS NOT MOVED
				// 1 - GO BACK TO ACTION MENU MODE
				OverworldView.getInstance().setMapMode(
						GlobalConsts.MAP_MODE_01_ACTION_MENU);
				// 2 - DISPLAY ACTION MENU
				displayActionMenu();
			}
		} else {
			// CHECK IF THE USER CLICKED A TRAVEL DESTINATION
			for (int i = 0; i < hexShapes.length; i++) {
				if (hexShapes[i].contains(x, y)) {
					// what hex was clicked?
					int source = BPHexMap.getInstance().getHexVertex(
							BPParty.getInstance().getCurrentHex());
					int target = hexShapes[i].getHexIndex();
					if (isFloating) {

					} else {
						// is this over a river?
						Turn.getInstance().addEvent(
								new LandTravelEvent(source, target));
						// are there any moves left?
					}
				}
			}
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMOActionMenuMode(final float x, final float y) {
		// ALLOW ACTION MENU TO HANDLE MOUSE OVER
		ActionMenu.getInstance().handleMouseOver(x, y);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMOFreeMode(final float x, final float y) {
		// how far am i from an edge?
		// define inner rectangle.
		boolean panPossible = false;
		if (isMouseInEdge(x, y)) {
			panPossible = checkForPanning(x, y);
		}
		if (panPossible) {
			// PANNING IS POSSIBLE - HANDLE MAP PANNING
			if (hasPanningTimerStarted()) {
				// check to see if it's time
				// to pan the map
				updatePanningTimer(Time.getInstance().getGameTime());
			} else { // start the panning timer
				startPanningTimer(Time.getInstance().getGameTime());
			}
		} else {
			// PANNING IS NOT POSSIBLE - STOP PANNING
			GameDrawable.setCursor(Cursor.DEFAULT_CURSOR);
			super.setPanning(GlobalConsts.DIR_NONE);
			// CHECK TO SEE IF MOUSE IS HOVERING PARTY SPRITE
			// if hovering is allowed
			if (OverworldView.getInstance().hasFlag(
					OverworldView.OWV_ALLOW_ACTION_MENU)
					&& isMouseOverPartySprite(x, y)) {
				GameDrawable.setCursor(Cursor.HAND_CURSOR);
				// HOVERING OVER PARTY SPRITE
				// START PARTY HOVER ANIMATION
				setPartyHoverIconPosition();
				super.assignFlag(GlobalConsts.MAP_FLAG_PARTY_HOVER);
				try {
					Animation.getInstance().playAnimation(
							partyHoverIcon,
							partyHoverIcon
									.getAnimation("party_cursor_hover"));
				} catch (Exception ex) {
					JOGLErrorHandler.getInstance().fatalError(ex);
				}
			} else {
				// NOT HOVERING OVER PARTY SPRITE
				// STOP PARTY HOVER ANIMATION
				removeFlag(GlobalConsts.MAP_FLAG_PARTY_HOVER);
				Animation.getInstance().stopAllAnimations(partyHoverIcon);
			}
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMOMoveSelectionMode(final float x, final float y) {
		// KILL THE PARTY HOVER ANIMATION
		removeFlag(GlobalConsts.MAP_FLAG_PARTY_HOVER);
		Animation.getInstance().stopAllAnimations(partyHoverIcon);
		// CHECK IF MOUSE OVER POSSIBLE TRAVEL LOCATIONS
		for (int i = 0; i < hexShapes.length; i++) {
			if (hexShapes[i].contains(x, y)) {

				hexHighlight.setPosition(
						hexShapes[i].getBounds().getX(),
						hexShapes[i].getBounds().getY(),
						super.getPosition().getZ()
								+ GlobalConsts.CHILD_STEP
								* (layers.length + 1)
						);
				try {
					Sprite.getInstance().setPartiallyRendered(
							hexHighlight,
							hexShapes[i].getBounds().getY()
									+ hexShapes[i].getBounds().getHeight(),
							hexShapes[i].getBounds().getX(),
							hexShapes[i].getBounds().getY(),
							hexShapes[i].getBounds().getX()
									+ hexShapes[i].getBounds().getWidth(),
							super.getPosition().getY() + size.height,
							super.getPosition().getX(),
							super.getPosition().getY(),
							super.getPosition().getX() + size.width);
				} catch (Exception ex) {
					JOGLErrorHandler.getInstance().fatalError(ex);
				}
				GameDrawable.setCursor(Cursor.HAND_CURSOR);
			}
		}
	}
	/**
	 * Handles events that occur when the user clicks on the map.
	 * @param e the {@link MouseEvent}
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	public void handleMouseClick(final MouseEvent e, final float x,
			final float y) {
		try {
			if (super.contains(x, y)) {
				switch (super.getMode()) {
				case GlobalConsts.MAP_MODE_00_FREE:
					handleMCFreeMode(e, x, y);
					break;
				case GlobalConsts.MAP_MODE_01_ACTION_MENU:
					handleMCActionMenuMode(e, x, y);
					break;
				case GlobalConsts.MAP_MODE_02_MOVE_SELECTION:
					handleMCMoveSelectionMode(e, x, y);
					break;
				case GlobalConsts.MAP_MODE_03_MOVE_ANIMATION:
					break;
				default:
					PooledStringBuilder sb =
							StringBuilderPool.getInstance().getStringBuilder();
					sb.append("OverworldMap set to invalid mode: ");
					sb.append(super.getMode());
					Exception ex = new Exception(sb.toString());
					sb.returnToPool();
					JOGLErrorHandler.getInstance().fatalError(ex);
					break;
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Handles events that occur when the user hovers an item.
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	public void handleMouseOver(final float x, final float y) {
		hexHighlight.setPosition(
				-64,
				-64,
				super.getPosition().getZ() + GlobalConsts.CHILD_STEP
						* (layers.length + 1)
				);
		if (super.contains(x, y)) {
			switch (super.getMode()) {
			case GlobalConsts.MAP_MODE_00_FREE:
				handleMOFreeMode(x, y);
				break;
			case GlobalConsts.MAP_MODE_01_ACTION_MENU:
				handleMOActionMenuMode(x, y);
				break;
			case GlobalConsts.MAP_MODE_02_MOVE_SELECTION:
				handleMOMoveSelectionMode(x, y);
				break;
			case GlobalConsts.MAP_MODE_03_MOVE_ANIMATION:
				break;
			default:
				PooledStringBuilder sb =
						StringBuilderPool.getInstance().getStringBuilder();
				sb.append("OverworldMap set to invalid mode: ");
				sb.append(super.getMode());
				Exception ex = new Exception(sb.toString());
				sb.returnToPool();
				JOGLErrorHandler.getInstance().fatalError(ex);
				break;
			}
		}
	}
	private void handleMoveSelection(final float x, final float y) {
		// is the cursor over a shape?
		for (int i = 0; i < hexShapes.length; i++) {
			if (hexShapes[i].contains(x, y)) {
				// what hex was clicked?
				int source = BPHexMap.getInstance().getHexVertex(
						BPParty.getInstance().getCurrentHex());
				int target = hexShapes[i].getHexIndex();
				if (isFloating) {

				} else {
					// is this over a river?
					Turn.getInstance().addEvent(
							new LandTravelEvent(source, target));
					// are there any moves left?
				}
			}
		}
	}
	/** Sets the flags to hide the action menu. */
	public void hideActionMenu() {
		OverworldView.getInstance().removeFlag(
				OverworldView.OWV_SHOW_ACTION_MENU);
		OverworldView.getInstance().assignFlag(
				OverworldView.OWV_ALLOW_ACTION_MENU);
	}
	/**
	 * Initializes the animation played when the cursor is hovering over the
	 * party.
	 */
	private void initializePartyHoverCursor() {
		try {
			partyHoverIcon =
					(BaseInteractiveObject) Interactive.getInstance()
							.getNewIO();
			SpriteBase sb = new BasicSpriteObject();
			SpriteImageObject img =
					SpriteImageObjectFactory.getInstance().getImageByName(
							"party_hover_icon_0");
			sb.setImageRefId(img.getRefId());
			sb.setWidth(img.getWidth());
			sb.setHeight(img.getHeight());
			sb.setPosition(new Vector3(0, 0, 0));
			partyHoverIcon.setSprite(sb);
			partyHoverIcon.addAnimation("party_cursor_hover",
					AnimationSequenceObjectFactory.getInstance()
							.getSequenceRefId("party_cursor_hover"));
			sb = null;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Determines if the mouse pointer is over the party sprite.
	 * @param x the x-coordinate of the mouse pointer
	 * @param y the y-coordinate of the mouse pointer
	 * @return true if the mouse is hovering over the party; false otherwise
	 */
	private boolean isMouseOverPartySprite(final float x, final float y) {
		double leftx = BPParty.getInstance().getWorldMapSprite().getSprite()
				.getPosition().getX();
		double bottomy = BPParty.getInstance().getWorldMapSprite().getSprite()
				.getPosition().getY();
		double mx = super.getViewX() + x - super.getPosition().getX();
		double my = super.getViewY() + y - super.getPosition().getY();
		boolean hover = false;
		if (mx >= leftx
				&& mx <= leftx + 32
				&& my >= bottomy
				&& my <= bottomy + 32) {
			hover = true;
		}
		return hover;
	}
	/**
	 * Loads the tile map.
	 * @param map the {@link TileMap}
	 * @throws Exception if an error occurs
	 */
	public void loadMap(final TileMap map) throws Exception {
		mapSize = map.getSize();
		double z = 0;
		if (super.getPosition() != null) {
			z = super.getPosition().getZ();
		}
		layers = new SpriteTileMap[map.getLayers() + 1];
		for (int i = 0; i < map.getLayers(); i++) {
			layers[i] = new SpriteTileMap(
					map.getCells(i),
					16,
					size.width,
					size.height,
					(float) (z + GlobalConsts.CHILD_STEP * i)
					);
			layers[i].setViewX(super.getViewX());
			layers[i].setViewY(super.getViewY());
			if (super.getPosition() != null) {
				layers[i].setPosition(
						super.getPosition().getX(),
						super.getPosition().getY(),
						z + GlobalConsts.CHILD_STEP * i
						);
			}
		}
		// create the sprite layer
		layers[layers.length - 1] = new SpriteTileMap(size.width, size.height);
		layers[layers.length - 1].addAnimatedImage(
				BPParty.getInstance().getWorldMapSprite());
		if (super.getPosition() != null) {
			layers[layers.length - 1].setPosition(
					super.getPosition().getX(),
					super.getPosition().getY(),
					z + GlobalConsts.CHILD_STEP * (layers.length - 1)
					);
		}
		for (int i = 0; i < hexShapes.length; i++) {
			hexShapes[i] = new TravelOption();
		}
		hexHighlight = new BasicSpriteObject();
		SpriteImageObject img =
				SpriteImageObjectFactory.getInstance().getImageByName(
						"hex_ovly_lt");
		hexHighlight.setImageRefId(img.getRefId());
		hexHighlight.setWidth(img.getWidth());
		hexHighlight.setHeight(img.getHeight());
		hexHighlight.setPosition(new Vector3(
				-64,
				-64,
				z + GlobalConsts.CHILD_STEP * (layers.length + 1)
				));
		for (int i = 0; i < hexLowlights.length; i++) {
			SpriteBase sb = new BasicSpriteObject();
			img = SpriteImageObjectFactory.getInstance().getImageByName(
							"hex_ovly_dk");
			sb.setImageRefId(img.getRefId());
			sb.setWidth(img.getWidth());
			sb.setHeight(img.getHeight());
			sb.setPosition(new Vector3(
					-64,
					-64,
					z + GlobalConsts.CHILD_STEP * (layers.length + 1)
					));
			hexLowlights[i] = sb;
			sb = null;
		}
		img = null;
		// create action menu
		ActionMenu.getInstance().setPreferredSize(1, 1);
		ActionMenu.getInstance().setPosition(
				0,
				0,
				z + GlobalConsts.CHILD_STEP * (layers.length + 1));
	}
	/**
	 * {@inheritDoc}
	 */
	private void playPartyHoverAnimation() {
		try {
			Animation.getInstance().playAnimation(
					partyHoverIcon,
					partyHoverIcon
							.getAnimation("party_cursor_hover"));
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void pan() {
		if (super.getPanning() == GlobalConsts.DIR_NONE) {
			for (int i = 0; i < layers.length; i++) {
				if (layers[i].isAnimatedLayer()) {
					layers[i].setFloatStoreNumber(0);
				}
			}
		} else {
			if (hasPanningShiftTimerStarted()) {
				if (readyForPanShift(Time.getInstance().getGameTime())) {
					switch (super.getPanning()) {
					case GlobalConsts.DIR_NNW:
						adjustViewX(-GlobalConsts.PAN_SHIFT_PX);
						adjustViewY(GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_N:
						adjustViewY(GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_NNE:
						adjustViewX(GlobalConsts.PAN_SHIFT_PX);
						adjustViewY(GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_E:
						adjustViewX(GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_SSE:
						adjustViewX(GlobalConsts.PAN_SHIFT_PX);
						adjustViewY(GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_S:
						adjustViewY(-GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_SSW:
						adjustViewX(-GlobalConsts.PAN_SHIFT_PX);
						adjustViewY(GlobalConsts.PAN_SHIFT_PX);
						break;
					default:
						adjustViewX(-GlobalConsts.PAN_SHIFT_PX);
						break;
					}
					if (super.getViewX() < 0) {
						super.setViewX(0);
					}
					if (super.getViewX() > mapSize.width * 16 - size.width) {
						super.setViewX(mapSize.width * 16 - size.width);
					}
					if (super.getViewY() < 0) {
						super.setViewY(0);
					}
					if (super.getViewY() > mapSize.height * 16
							- size.height) {
						super.setViewY(mapSize.height * 16 - size.height);
					}
					for (int i = 0; i < layers.length; i++) {
						layers[i].setViewX(super.getViewX());
						layers[i].setViewY(super.getViewY());
						layers[i].setFloatStoreNumber(0);
					}
					setPanningShiftTimer(Time.getInstance().getGameTime());
				}
				super.setPanning(GlobalConsts.DIR_NONE);
			} else {
				setPanningShiftTimer(Time.getInstance().getGameTime());
			}
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void playHighlightAnimation(int refId) {
		// TODO Auto-generated method stub
		
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void prepareForRendering() throws Exception {
		if (super.isVisible()
				&& layers != null) {
			pan();
			if (super.getMode() == GlobalConsts.MAP_MODE_02_MOVE_SELECTION) {
				// show the hex highlights
				for (int i = 0; i < hexLowlights.length; i++) {
					if (hexLowlights[i].getPosition().getX() > -64
							&& hexLowlights[i].getPosition().getX() <
							super.getPosition().getX() + size.width) {
						Render.getInstance().registerEntity(hexLowlights[i]);
					}
				}
				if (hexHighlight.getPosition().getX() > -64) {
					Render.getInstance().registerEntity(hexHighlight);
				}
			}
			for (int i = 0; i < layers.length; i++) {
				Render.getInstance().registerEntity(layers[i]);
			}
			if (hasFlag(GlobalConsts.MAP_FLAG_PARTY_HOVER)) {
				Render.getInstance().registerEntity(partyHoverIcon.getSprite());
			}
			if (OverworldView.getInstance().hasFlag(
					OverworldView.OWV_SHOW_ACTION_MENU)) {
				ActionMenu.getInstance().prepareForRendering();
			}
		}
	}
	public void setMoveRadius(final Integer... vertices) {
		// the maximum x and y positions that can be rendered on
		double maxX = super.getPosition().getX() + size.width;
		double maxY = super.getPosition().getY() + size.height;
		// the last index used for extra lowlights added for edge hexes that
		// aren't reachable
		int lastExtra = 460;
		// clear out all highlights
		for (int i = 0; i < hexShapes.length; i++) {
			hexShapes[i].reset();
		}
		int partyHexIndex = BPHexMap.getInstance().getHexVertex(
				BPParty.getInstance().getCurrentHex());
		for (int i = 460; i < hexLowlights.length; i++) {
			hexLowlights[i].setPosition(
					-64,
					-64,
					hexLowlights[i].getPosition().getZ()
					);
		}
		for (int outer = 0; outer < 460; outer++) {
			// define highlight's position
			Vector2 v = BPHexMap.getInstance().getHexVector(outer);
			v = BPHexMap.getInstance().getHexLeftBottomPoint(
					(int) v.getX(), (int) v.getY());
			double ax = v.getX();
			ax -= super.getViewX();
			ax += super.getPosition().getX();
			double ay = v.getY();
			ay -= super.getViewY();
			ay += super.getPosition().getY();
			double az = super.getPosition().getZ();
			az += GlobalConsts.CHILD_STEP * layers.length;
			boolean skip = partyHexIndex == outer;
			if (skip) {
				try {
					lastExtra =
							applyAdditionalHexShading(outer, ax, ay, az,
									lastExtra);
				} catch (Exception ex) {
					JOGLErrorHandler.getInstance().fatalError(ex);
				}
			}
			if (!skip) {
				for (int inner = 0; inner < vertices.length; inner++) {
					if (vertices[inner] == outer) {
						skip = true;
						break;
					}
				}
				if (skip) {
					try {
						lastExtra =
								applyAdditionalHexShading(outer, ax, ay, az,
										lastExtra);
					} catch (Exception ex) {
						JOGLErrorHandler.getInstance().fatalError(ex);
					}
				}
				if (!skip) {
					// check left and right edges
					if (ax <= super.getPosition().getX() - 64
							|| ax >= super.getPosition().getX() + size.width) {
						// if left edge is outside map's left edge or right
						// edge, do not render
						skip = true;
					}
					if (skip) {
						try {
							lastExtra =
									applyAdditionalHexShading(outer, ax, ay,
											az,
											lastExtra);
						} catch (Exception ex) {
							JOGLErrorHandler.getInstance().fatalError(ex);
						}
					}
					if (!skip) {
						// check top and bottom edges
						if (ay <= super.getPosition().getY() - 64
								|| ay >=
									super.getPosition().getY() + size.height) {
							// if bottom edge is outside map's bottom edge or
							// top edge, do not render
							skip = true;
						}
					}
					if (skip) {
						try {
							lastExtra =
									applyAdditionalHexShading(outer, ax, ay,
											az,
											lastExtra);
						} catch (Exception ex) {
							JOGLErrorHandler.getInstance().fatalError(ex);
						}
					}
				}
			}
			if (skip) {
				hexLowlights[outer].setPosition(
						-64,
						-64,
						hexLowlights[outer].getPosition().getZ()
						);
				continue;
			} else {
				try {
					lastExtra =
							applyAdditionalHexShading(outer, ax, ay, az,
									lastExtra);
					// get the position of the highlight
					hexLowlights[outer].setPosition(ax, ay, az);
					Sprite.getInstance().setPartiallyRendered(
							hexLowlights[outer],
							ay + hexLowlights[outer].getHeight(),
							ax,
							ay,
							ax + hexLowlights[outer].getWidth(),
							super.getPosition().getY() + size.height,
							super.getPosition().getX(),
							super.getPosition().getY(),
							super.getPosition().getX() + size.width);
				} catch (Exception ex) {
					JOGLErrorHandler.getInstance().fatalError(ex);
				}
			}
			v = null;
		}

	}
	/** Sets the position of the party hover animation. */
	private void setPartyHoverIconPosition() {
		double ax = BPParty.getInstance().getWorldMapSprite().getSprite()
				.getPosition().getX();
		ax -= super.getViewX();
		ax += super.getPosition().getX();
		double ay = BPParty.getInstance().getWorldMapSprite().getSprite()
				.getPosition().getY();
		ay -= super.getViewY();
		ay += super.getPosition().getY();
		double az = 0;
		if (super.getPosition() != null) {
			az = super.getPosition().getZ();
		}
		az += GlobalConsts.CHILD_STEP * layers.length;
		partyHoverIcon.getSprite().setPosition(ax, ay, az);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setPosition(final double x, final double y, final double z) {
		super.setPosition(x, y, z);
		if (layers != null) {
			for (int i = 0; i < layers.length; i++) {
				layers[i].setPosition(x, y, z + GlobalConsts.CHILD_STEP * i);
			}
		}
	}
	/**
	 * Sets possible travel moves, displaying highlights over the selected
	 * hexes.
	 * @param vertices the list of vertices
	 */
	public void setPossibleTravelMoves(final Integer... vertices) {
		setTravelOptionShapes(vertices);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setPreferredSize(int w, int h) {
		size.width = w;
		size.height = h;
	}
	/**
	 * Defines the area each travel hex will cover, and the map hex to which it
	 * corresponds.
	 * @param vertices the list of vertices
	 */
	private void setTravelOptionShapes(final Integer... vertices) {
		// define new shapes for travel locations
		for (int i = 0; i < vertices.length; i++) {
			// get the position of the highlight
			Vector2 v = BPHexMap.getInstance().getHexVector(vertices[i]);
			v = BPHexMap.getInstance().getHexLeftBottomPoint(
					(int) v.getX(), (int) v.getY());
			double ax = v.getX();
			ax -= super.getViewX();
			ax += super.getPosition().getX();
			double ay = v.getY();
			ay -= super.getViewY();
			ay += super.getPosition().getY();
			// define points

			// start with bottom left
			hexShapes[i].addPoint((int) ax + 16, (int) ay);
			hexShapes[i].addPoint((int) ax, (int) ay + 31);
			hexShapes[i].addPoint((int) ax, (int) ay + 32);
			hexShapes[i].addPoint((int) ax + 16, (int) ay + 64);
			hexShapes[i].addPoint((int) ax + 47, (int) ay + 64);
			hexShapes[i].addPoint((int) ax + 64, (int) ay + 32);
			hexShapes[i].addPoint((int) ax + 64, (int) ay + 31);
			hexShapes[i].addPoint((int) ax + 47, (int) ay);
			hexShapes[i].setHexIndex(vertices[i]);
		}
	}
}

/**
 * Defines the area on screen of a map hex during the movement phase, and also
 * the index of the map hex.
 * @author DaLoneDrau
 */
final class TravelOption extends Polygon {
	/**
	 * 
	 */
	private static final long	serialVersionUID	= 1L;
	/** the index of the map hex this shape represents. */
	private int					hexIndex;
	/**
	 * Gets the index of the map hex this shape represents.
	 * @return <code>int</code>
	 */
	int getHexIndex() {
		return hexIndex;
	}
	/**
	 * Sets the index of the map hex this shape represents.
	 * @param index the index
	 */
	void setHexIndex(final int index) {
		hexIndex = index;
	}
}
